package com.nulldev.util.net.packets;

import java.net.InetAddress;

import com.nulldev.util.crypto.encryptAPI.rsa.RSA;
import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.java.Variables;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.napi.nullAPI;
import com.nulldev.util.net.experimental.udp2.Connection;
import com.nulldev.util.net.experimental.udp2.NetworkSendType;
import com.nulldev.util.net.tcp.client.TCPClient.ClientConnection;
import com.nulldev.util.net.tcp.server.TCPServer.ServerConnection;
import com.nulldev.util.net.udp.UDPServer;
import com.nulldev.util.other.ArrayUtils;
import com.nulldev.util.other.ByteUtils;
import com.nulldev.util.threading.legacy.ThreadExecutor;

public class NetInfo {

	public final nullAPI apiCalls = PacketManager.getAPICalls();
	
	private final String ip;
	private final int port;
	private final Object obj;
	private static final Logger log = LoggerFactory.getLogger(true, false);
	
	private long readTime;
	
	public NetInfo(final String ip, final int port, final Object source) {
		this.ip = ip;
		this.port = port;
		this.obj = source;
	}
	
	public NetInfo applyReadTimeData(final long readTime) {
		this.readTime = readTime;
		return this;
	}
	
	public String getIP() {
		return this.ip;
	}
	
	public int getPort() {
		return this.port;
	}
	
	public void reply(final NetPacket packet) {
		if (this.obj instanceof ServerConnection) {
			final ServerConnection cc = (ServerConnection) this.obj;
			cc.sendBlocking(packet);
		} else if (this.obj instanceof ClientConnection) {
			final ClientConnection cc = (ClientConnection) this.obj;
			cc.sendBlocking(packet);
		} else if (this.obj instanceof UDPServer) {
			final UDPServer cc = (UDPServer) this.obj;
			try {
				cc.reply(packet, InetAddress.getByName(this.ip), this.port);
			} catch (Exception e) {
				e.printStackTrace();
			}
		} else if (this.obj instanceof Connection) {
			final Connection conn = (Connection) this.obj;
			conn.addToSendQue(packet.toString().getBytes(), NetworkSendType.RELIABLE_DATA);
		}
	}

	public void sendBlocking(final NetPacket packet) {
		if (Variables.isNull(packet)) {
			log.error("sendBlocking(packet) -> Packet is null! Halting.");
			return;
		}
		ThreadExecutor.execute(new Runnable() {
			
			@Override
			public void run() {
				try {
					NetInfo.this.reply(packet);
				}catch (Exception e) {
					log.error("Failed to reply!", e);
				}
			}
		});
	}

	public String getKey() {
		if (this.obj instanceof ServerConnection) {
			final ServerConnection cc = (ServerConnection) this.obj;
			return cc.getKey();
		}else if (this.obj instanceof ClientConnection) {
			final ClientConnection cc = (ClientConnection) this.obj;
			return cc.getKey();
		}
		return "";
	}

	public String format() {
		if (this.obj instanceof ServerConnection) {
			final ServerConnection cc = (ServerConnection) this.obj;
			return cc.format();
		}else if (this.obj instanceof ClientConnection) {
			final ClientConnection cc = (ClientConnection) this.obj;
			return cc.format();
		}else if (this.obj instanceof UDPServer) {
			final UDPServer cc = (UDPServer) this.obj;
			return cc.format();
		}else if (this.obj instanceof Connection) {
			final Connection conn = (Connection) this.obj;
			return conn.getAddress().getHostAddress() + ":" + conn.getPort();
		}
		return "";
	}

	public boolean setKey(final String data) {
		if (this.obj instanceof ServerConnection) {
			final ServerConnection cc = (ServerConnection) this.obj;
			cc.setKey(data);
			return true;
		}else if (this.obj instanceof ClientConnection) {
			final ClientConnection cc = (ClientConnection) this.obj;
			cc.setKey(data);
			return true;
		}
		return false;
	}

	public String aformat() {
		if (this.obj instanceof ClientConnection) {
			final ClientConnection cc = (ClientConnection) this.obj;
			return cc.getDefIP();
		}
		return format();
	}

	public void setDefIP(String data) {
		if (this.obj instanceof ClientConnection) {
			final ClientConnection cc = (ClientConnection) this.obj;
			cc.setDefIP(data);
		}
	}
	
	public String getDefIP() {
		if (this.obj instanceof ClientConnection) {
			final ClientConnection cc = (ClientConnection) this.obj;
			return cc.getDefIP();
		}
		return "";
	}

	public void disconnect() {
		if (this.obj instanceof ClientConnection) {
			final ClientConnection cc = (ClientConnection) this.obj;
			cc.callDisconnect();
		}
	}

	public boolean isSDTPSupported() {
		if (this.obj instanceof ServerConnection) {
			final ServerConnection conn = (ServerConnection) this.obj;
			return conn.isSDTPSupported();
		}
		return false;
	}
	
	public ServerConnection getServerConnection() {
		if (this.obj instanceof ServerConnection) {
			final ServerConnection conn = (ServerConnection) this.obj;
			return conn;
		}
		log.warn("getServerConnection() -> Hit null return, something went wrong.");
		return null;
	}

	public nullAPI getAPI() {
		return this.apiCalls;
	}

	public Logger getlog() {
		return this.log;
	}

	public Object getObj() {
		return this.obj;
	}

	public <T> Object invoke(final String command, final T object) {
		return this.apiCalls.invoke(command, this.obj, object);
	}
	
	public <T> Object invoke(final String command) {
		return this.apiCalls.invoke(command, this.obj);
	}

	public <T> Object invoke(final String command, final Object... object) {
		return this.apiCalls.invoke(command, ArrayUtils.<Object>addAll(new Object[] {this.obj}, object));
	}
	
	public <T> Object invokeAll(final String command, final T... object) {
		return this.apiCalls.invoke(command, ArrayUtils.<Object>addAll(new Object[] {this.obj}, object));
	}

	public void onRemoved(final String reason) {
		if (this.obj instanceof ClientConnection) {
			final ClientConnection conn = (ClientConnection) this.obj;
			conn.onRemoved(reason);
		}
	}

	public boolean setPublicKey(final byte[] keyData) {
		if (this.obj instanceof ServerConnection) {
			final ServerConnection cc = (ServerConnection) this.obj;
			cc.setPublicKey(keyData);
			return true;
		}else if (this.obj instanceof ClientConnection) {
			final ClientConnection cc = (ClientConnection) this.obj;
			cc.setPublicKey(keyData);
			return true;
		}
		return false;
	}
	
	public boolean setServerPublicKey(final byte[] keyData) {
		if (this.obj instanceof ClientConnection) {
			final ClientConnection conn = (ClientConnection) this.obj;
			conn.setServerPublicKey(keyData);
			return true;
		}
		return false;
	}
	
	public boolean hasPublicKey() {
		if (this.obj instanceof ServerConnection) {
			final ServerConnection cc = (ServerConnection) this.obj;
			return cc.hasPublicKey();
		}else if (this.obj instanceof ClientConnection) {
			final ClientConnection cc = (ClientConnection) this.obj;
			return cc.hasPublicKey();
		}
		return false;
	}
	
	public byte[] getPublicKey() {
		if (this.obj instanceof ServerConnection) {
			final ServerConnection cc = (ServerConnection) this.obj;
			return cc.getPublicKey();
		}else if (this.obj instanceof ClientConnection) {
			final ClientConnection cc = (ClientConnection) this.obj;
			return cc.getPublicKey();
		}
		return null;
	}
	
	public String attemptEncryptionV3(final NetPacket packet) {
		if (packet == null) {
			throw new RuntimeException("packet == null!");
		}
		final boolean hasPublicKey = this.hasPublicKey();
		if (hasPublicKey) {
			final byte[] pubKey = this.getPublicKey();
			final String data = ByteUtils.toBase64(RSA.encrypt(RSA.getPublicKey(pubKey), packet.getData()));
			return "[NP3{" + packet.getID() + ",ENC_NCRYPT}]" + data;
		}else if (this.hasServerPublicKey()) {
			return this.attemptEncryptionV3Server(packet);
		}
		return packet.toString();
	}
	
	public byte[] attemptEncryptionV3Bytes(final NetPacket packet) {
		if (packet == null) {
			throw new RuntimeException("packet == null!");
		}
		final boolean hasPublicKey = this.hasPublicKey();
		if (hasPublicKey) {
			//JVMUtil.async_println("Encrypting using public key from client...");
			final byte[] pubKey = this.getPublicKey();
			final byte[] data = RSA.encrypt(RSA.getPublicKey(pubKey), packet.getData());
			return data;
		}else if (this.hasServerPublicKey()) {
			//JVMUtil.async_println("Encrypting using public key from server...");
			return this.attemptEncryptionV3BytesServer(packet);
		}
		return null;
	}
	
	public byte[] attemptEncryptionV3Bytes(final String data) {
		if (data == null) {
			throw new RuntimeException("packet == null!");
		}
		final boolean hasPublicKey = this.hasPublicKey();
		if (hasPublicKey) {
			final byte[] pubKey = this.getPublicKey();
			final byte[] data0 = RSA.encrypt(RSA.getPublicKey(pubKey), data.getBytes());
			return data0;
		}else if (this.hasServerPublicKey()) {
			return this.attemptEncryptionV3BytesServer(data);
		}
		return null;
	}
	
	public String attemptEncryptionV3Server(final NetPacket packet) {
		if (packet == null) {
			throw new RuntimeException("packet == null!");
		}
		final boolean hasPublicKey = this.hasServerPublicKey();
		if (hasPublicKey) {
			final byte[] pubKey = this.getServerPublicKey();
			final String data = ByteUtils.toBase64(RSA.encrypt(RSA.getPublicKey(pubKey), packet.getData()));
			return "[NP3{" + packet.getID() + ",ENC_NCRYPT}]" + data;
		}
		return packet.toString();
	}
	
	public byte[] attemptEncryptionV3BytesServer(final NetPacket packet) {
		if (packet == null) {
			throw new RuntimeException("packet == null!");
		}
		final boolean hasPublicKey = this.hasServerPublicKey();
		if (hasPublicKey) {
			final byte[] pubKey = this.getServerPublicKey();
			final byte[] data = RSA.encrypt(RSA.getPublicKey(pubKey), packet.getData());
			return data;
		}
		return null;
	}
	
	public byte[] attemptEncryptionV3BytesServer(final String data) {
		if (data == null) {
			throw new RuntimeException("packet == null!");
		}
		final boolean hasPublicKey = this.hasServerPublicKey();
		if (hasPublicKey) {
			final byte[] pubKey = this.getServerPublicKey();
			final byte[] data0 = RSA.encrypt(RSA.getPublicKey(pubKey), data.getBytes());
			return data0;
		}
		return null;
	}
	
	public byte[] getServerPublicKey() {
		if (this.obj instanceof ClientConnection) {
			final ClientConnection conn = (ClientConnection) this.obj;
			return conn.getTCPClient().getServerPublicKey();
		}
		return this.getPublicKey();
	}
	
	public boolean hasServerPublicKey() {
		if (this.obj instanceof ClientConnection) {
			final ClientConnection conn = (ClientConnection) this.obj;
			return conn.getTCPClient().hasServerPublicKey();
		}
		return false;
	}
	
	public Object get() {
		return this.obj;
	}

	public long readTime() {
		return this.readTime;
	}

}
